<template>
  <view class="chatInterface" @contextmenu.prevent="">
    <image class="group-icon" src="/static/message/group-icon.png" @click="showMembers"/>
    <view class="scroll-view">
      <image v-if="history.loading" class="history-loaded" src="/static/message/loading.svg"/>
      <view v-else :class="history.allLoaded ? 'history-loaded':'load'" @click="loadHistoryMessage(false)">
        <view>{{ history.allLoaded ? '已经没有更多的历史消息' : '点击获取历史消息' }}</view>
      </view>

      <checkbox-group @change="selectMessages">
        <view v-for="(message,index) in history.messages" :key="message.messageId">
          <!--时间显示，类似于微信，隔5分钟不发言，才显示时间-->
          <view class="time-lag">
            {{ renderMessageDate(message, index) }}
          </view>
          <view class="message-recalled" v-if="message.recalled">
            <view v-if="message.recaller.id === currentUser.id" class="message-recalled-self">
              <view>你撤回了一条消息</view>
              <span v-if="message.type === 'text' && Date.now()-message.timestamp< 60 * 1000 "
                    @click="editRecalledMessage(message.payload.text)">重新编辑</span>
            </view>
            <view v-else>{{ message.recaller.data.name }}撤回了一条消息</view>
          </view>
          <view class="message-item" v-else>
            <view class="message-item-checkbox">
              <checkbox v-show="messageSelector.visible && message.status !== 'sending'" :value="message.messageId"
                        :checked="messageSelector.messages.includes(message)"/>
            </view>
            <view class="message-item-content" :class="{'self' : message.senderId ===  currentUser.id}">
              <view class="avatar">
                <image :src="message.senderData.avatar"></image>
              </view>

              <view class="content" @click.right="showActionPopup(message)" @longpress="showActionPopup(message)">
                <view class="message-payload">
<!--                  <b class="pending" v-if="message.status === 'sending'"></b>-->
                  <b class="send-fail" v-if="message.status === 'fail'"></b>
                  <view v-if="message.type === 'text'" class="text-content" v-html="renderTextMessage(message)"></view>
                  <image v-if="message.type === 'image'"
                         :data-url="message.payload.url"
                         :src="message.payload.thumbnail"
                         class="image-content"
                         mode="heightFix"
                         @click="showImageFullScreen"
                  ></image>
                  <view class="video-snapshot" v-if="message.type === 'video'" :data-url="message.payload.video.url"
                        @click="playVideo">
                    <image
                      :src="message.payload.thumbnail.url"
                      :style="{height: getImageHeight(message.payload.thumbnail.width,message.payload.thumbnail.height)+'rpx' }"
                      mode="heightFix"
                    ></image>
                    <view class="video-play-icon"></view>
                  </view>
                  <view class="file-content" v-if="message.type === 'file'">
                    <view class="file-info">
                      <span class="file-name">{{ message.payload.name }}</span>
                      <span class="file-size">{{ (message.payload.size / 1024).toFixed(2) }}KB</span>
                    </view>
                    <image class="file-img" src="/static/message/file-icon.png"></image>
                  </view>
                  <view v-if="message.type ==='audio'" class="audio-content" @click="playAudio(message)">
                    <view class="audio-facade" :style="{width:Math.ceil(message.payload.duration)*7 + 50 + 'px'}">
                      <view
                        class="audio-facade-bg"
                        :class="{'play-icon':audioPlayer.playingMessage && audioPlayer.playingMessage.messageId === message.messageId}"
                      ></view>
                      <view>{{Math.ceil(message.payload.duration) || 1}}<span>"</span></view>
                    </view>
                  </view>
                  <view v-if="message.type === 'order'" class="order-content">
                    <view class="order-id">订单号：{{ message.payload.id }}</view>
                    <view class="order-body">
                      <image :src="message.payload.url" class="order-img"></image>
                      <view>
                        <view class="order-name">{{ message.payload.name }}</view>
                        <view class="order-info">
                          <view class="order-price">{{ message.payload.price }}</view>
                          <view class="order-count">共{{ message.payload.count }}件</view>
                        </view>
                      </view>
                    </view>
                  </view>
                </view>
              </view>
            </view>
          </view>
        </view>
      </checkbox-group>
    </view>
    <view class="action-box" v-if="!videoPlayer.visible && !messageSelector.visible">
      <view class="action-top">
        <view @click="switchAudioKeyboard">
          <image class="more" v-if="audio.visible" src="/static/message/jianpan.png"></image>
          <image class="more" v-else src="/static/message/audio.png"></image>
        </view>
        <view v-if="audio.visible" class="record-input" @touchend.stop="onRecordEnd" @touchstart.stop="onRecordStart">
          {{ recorderManager.recording ? '松开发送' : '按住录音' }}
        </view>
        <!-- GoEasyIM最大支持3k的文本消息，如需发送长文本，需调整输入框maxlength值 -->
        <input v-else v-model="text" @confirm="sendTextMessage" class="consult-input" maxlength="700" placeholder="发送消息" type="text" />
        <view @click="switchEmojiKeyboard">
          <image class="more" v-if="emoji.visible" src="/static/message/jianpan.png"></image>
          <image class="more" v-else src="/static/images/emoji.png"></image>
        </view>
        <view>
          <image @click="showOtherTypesMessagePanel()" class="more" src="/static/message/more.png"/>
        </view>
        <view v-if="text" class="send-btn-box">
          <text class="btn" @click="sendTextMessage()">发送</text>
        </view>
      </view>
      <view class="action-bottom action-bottom-emoji" v-if="emoji.visible">
        <image class="emoji-item" v-for="(emojiItem, key, index) in emoji.map" :key="index" :src="emoji.url + emojiItem"
               @click="chooseEmoji(key)"></image>
      </view>
      <!--其他类型消息面板-->
      <view v-if="otherTypesMessagePanelVisible" class="action-bottom">
        <view class="more-icon">
          <image @click="sendImageMessage()" class="operation-icon" src="/static/message/picture.png"></image>
          <view class="operation-title">图片</view>
        </view>
        <view class="more-icon">
          <image @click="sendVideoMessage()" class="operation-icon" src="/static/message/video.png"></image>
          <view class="operation-title">视频</view>
        </view>
        <view class="more-icon">
          <image @click="showOrderMessageList()" class="operation-icon" src="/static/message/order.png"></image>
          <view class="operation-title">订单</view>
        </view>
        <view class="more-icon">
          <image @click="groupCall()" class="operation-icon" src="/static/message/video.png"></image>
          <view class="operation-title">视频通话</view>
        </view>
      </view>
    </view>
    <view class="action-popup" @touchmove.stop.prevent v-if="actionPopup.visible">
      <view class="layer"></view>
      <view class="action-list">
        <view class="action-item" @click="deleteSingleMessage">删除</view>
        <view class="action-item" v-if="actionPopup.recallable" @click="recallMessage">撤回</view>
        <view class="action-item" @click="showCheckBox">多选</view>
        <view class="action-item" @click="hideActionPopup">取消</view>
      </view>
    </view>
    <view class="messageSelector-box" v-if="messageSelector.visible">
      <image class="messageSelector-btn" @click="deleteMultipleMessages" src="/static/message/delete.png"></image>
    </view>
    <view class="record-loading" v-if="recorderManager.recording"></view>
    <video v-if="videoPlayer.visible" :src="videoPlayer.url" id="videoPlayer"
           @fullscreenchange="onVideoFullScreenChange"></video>
    <view v-if="orderList.visible" class="order-list">
      <view class="orders-content">
        <view class="title">
          <view>请选择一个订单</view>
          <view class="close" @click="hideOrderMessageList">×</view>
        </view>
        <view class="orders">
          <view
            v-for="(order, index) in orderList.orders"
            :key="index" class="order-item"
            @click="sendOrderMessage(order)"
          >
            <view class="order-id">订单号：{{ order.id }}</view>
            <view class="order-body">
              <image :src="order.url" class="order-img"></image>
              <view class="order-name">{{ order.name }}</view>
              <view class="order-right">
                <view class="order-price">{{ order.price }}</view>
                <view class="order-count">共{{ order.count }}件</view>
              </view>
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import EmojiDecoder from '@/common/lib/EmojiDecoder'
import RecorderManager from '@/common/lib/RecorderManager'
import store from "@/store"
import { formatTime } from '@/utils/util'

const IMAGE_MAX_WIDTH = 200
const IMAGE_MAX_HEIGHT = 150
const recorderManager = new RecorderManager()

export default {
  name: 'groupChat',
  data() {
    // 定义表情
    const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/'
    const emojiMap = {
      '[么么哒]': 'emoji_3@2x.png',
      '[乒乓]': 'emoji_4@2x.png',
      '[便便]': 'emoji_5@2x.png',
      '[信封]': 'emoji_6@2x.png',
      '[偷笑]': 'emoji_7@2x.png',
      '[傲慢]': 'emoji_8@2x.png'
    }
    return {
      formatTime,
      //聊天文本框
      text: '',
      group: null,
      to: {},// 作为createMessage的参数
      //定义表情列表
      emoji: {
        url: emojiUrl,
        map: emojiMap,
        visible: false,
        decoder: new EmojiDecoder(emojiUrl, emojiMap),
      },
      //是否展示‘其他消息类型面板’
      otherTypesMessagePanelVisible: false,
      orderList: {
        orders: [],
        visible: false
      },
      history: {
        messages: [],
        allLoaded: false,
        loading: false
      },
      recorderManager: recorderManager,
      audio: {
        //录音按钮展示
        visible: false
      },
      audioPlayer: {
        innerAudioContext: null,
        playingMessage: null,
      },
      videoPlayer: {
        visible: false,
        url: '',
        context: null
      },
      // 展示消息删除弹出框
      actionPopup: {
        visible: false,
        message: null,
        recallable: false,
      },
      // 消息选择
      messageSelector: {
        visible: false,
        messages: []
      }
    }
  },
  computed: {
    currentUser() {
      return store.getters.messageUser
    }
  },
  onLoad(options) {
    //聊天对象
    this.group = []
    this.groupMembers = []
    this.to = {
      id: this.group.id,
      type: this.$GoEasy.IM_SCENE.GROUP,
      data: {
        name: this.group.name,
        avatar: this.group.avatar
      }
    }

    this.initGoEasyListeners()
    // 语音播放器
    this.initialAudioPlayer()
    // 录音监听器
    this.initRecorderListeners()

  },
  onShow() {
    this.otherTypesMessagePanelVisible = false
    this.emoji.visible = false
  },
  onReady() {
    this.loadHistoryMessage(true)
    this.videoPlayer.context = uni.createVideoContext('videoPlayer', this)
    uni.setNavigationBarTitle({
      title: this.group.name + '（' + Object.keys(this.groupMembers).length + '）'
    })
  },
  onPullDownRefresh(e) {
    this.loadHistoryMessage(false)
  },
  onUnload() {
    //退出聊天页面之前，清空监听器
    this.$GoEasy.im.off(this.$GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this.onMessageReceived)
    this.$GoEasy.im.off(this.$GoEasy.IM_EVENT.MESSAGE_DELETED, this.onMessageDeleted)
  },
  methods: {
    //渲染文本消息，如果包含表情，替换为图片
    //todo:本不需要该方法，可以在标签里完成，但小程序有兼容性问题，被迫这样实现
    renderTextMessage(message) {
      return '<span>' + this.emoji.decoder.decode(message.payload.text) + '</span>'
    },
    //像微信那样显示时间，如果有几分钟没发消息了，才显示时间
    //todo:本不需要该方法，可以在标签里完成，但小程序有兼容性问题，被迫这样实现
    renderMessageDate(message, index) {
      if (index === 0) {
        return this.formatTime(message.timestamp)
      } else {
        if (message.timestamp - this.history.messages[index - 1].timestamp > 5 * 60 * 1000) {
          return this.formatTime(message.timestamp)
        }
      }
      return ''
    },
    initGoEasyListeners() {
      // 监听群聊消息
      this.$GoEasy.im.on(this.$GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this.onMessageReceived)
      //监听消息删除
      this.$GoEasy.im.on(this.$GoEasy.IM_EVENT.MESSAGE_DELETED, this.onMessageDeleted)
    },
    onMessageReceived (message) {
      let groupId = message.groupId
      if (groupId === this.group.id) {
        this.history.messages.push(message)
        //聊天时，收到消息标记为已读
        this.markGroupMessageAsRead()
        //收到新消息，是滚动到最底部
        this.scrollToBottom()
      }
    },
    onMessageDeleted (deletedMessages) {
      deletedMessages.forEach(message => {
        let groupId = message.groupId
        if (groupId && groupId === this.group.id) {
          let index = this.history.messages.indexOf(message)
          if (index > -1) {
            this.history.messages.splice(index, 1)
          }
        }
      })
    },
    initialAudioPlayer () {
      this.audioPlayer.innerAudioContext = uni.createInnerAudioContext()
      this.audioPlayer.innerAudioContext.onEnded(() => {
        this.audioPlayer.playingMessage = null
      })
      this.audioPlayer.innerAudioContext.onStop(() => {
        this.audioPlayer.playingMessage = null
      })
    },
    initRecorderListeners() {
      recorderManager.onRecordComplete((file, duration) => {
        if (duration < 1000) return this.$toast('录音时间太短')
        this.$GoEasy.im.createAudioMessage({
          to: this.to,
          file: file,
          notification: {
            title: this.currentUser.name + '发来一段语音',
            body: '[语音消息]',		// 字段最长 50 字符
            sound: 'message',
            badge: '+1'
          },
          onProgress: function (progress) {
            console.log(progress)
          },
          onSuccess: (message) => {
            this.sendMessage(message)
          },
          onFailed: (e) => {
            console.log('error :', e)
          }
        })
      })
    },
    /**
     * 核心就是设置高度，产生明确占位
     *
     * 小  (宽度和高度都小于预设尺寸)
     *    设高=原始高度
     * 宽 (宽度>高度)
     *    高度= 根据宽度等比缩放
     * 窄  (宽度<高度)或方(宽度=高度)
     *    设高=MAX height
     *
     * @param width
     * @param height
     * @returns number
     */
    getImageHeight(width, height) {
      if (width < IMAGE_MAX_WIDTH && height < IMAGE_MAX_HEIGHT) {
        return height * 2
      } else if (width > height) {
        return (IMAGE_MAX_WIDTH / width * height) * 2
      } else if (width === height || width < height) {
        return IMAGE_MAX_HEIGHT * 2
      }
    },
    sendMessage(message) {
      this.history.messages.push(message)
      this.scrollToBottom()
      this.$GoEasy.im.sendMessage({
        message: message,
        onSuccess: function (message) {
          console.log('发送成功.', message)
        },
        onFailed: function (error) {
          if (error.code === 507) {
            console.log('发送语音/图片/视频/文件失败，没有配置OSS存储，详情参考：https://docs.goeasy.io/2.x/im/message/media/alioss')
          } else {
            console.log('发送失败:', error)
          }
        }
      })
    },
    sendTextMessage() {
      console.log(this.currentUser)
      if (this.text.trim() !== '') {
        let body = this.text
        if (this.text.length >= 50) {
          body = this.text.substring(0, 30) + '...'
        }
        this.$GoEasy.im.createTextMessage({
          text: this.text,
          to: this.to,
          notification: {
            title: this.currentUser.name + '发来一段文字',
            body: body,
            sound: 'message',
            badge: '+1'
          },
          onSuccess: (message) => {
            this.sendMessage(message)
          },
          onFailed: (e) => {
            console.log('error :', e)
          }
        })
      }
      this.text = ''
    },
    sendVideoMessage() {
      uni.chooseVideo({
        success: (res) => {
          this.$GoEasy.im.createVideoMessage({
            to: this.to,
            file: res,
            notification: {
              title: this.currentUser.name + '发来一个视频',
              body: '[视频消息]',		// 字段最长 50 字符
              sound: 'message',
              badge: '+1'
            },
            onProgress: function (progress) {
              console.log(progress)
            },
            onSuccess: (message) => {
              this.otherTypesMessagePanelVisible = false
              this.sendMessage(message)
            },
            onFailed: (e) => {
              console.log('error :', e)
            }
          })
        }
      })
    },
    sendImageMessage() {
      uni.chooseImage({
        count: 9,
        success: (res) => {
          res.tempFiles.forEach(file => {
            this.$GoEasy.im.createImageMessage({
              to: this.to,
              file: file,
              notification: {
                title: this.currentUser.name + '发来一张图片',
                body: '[图片消息]',		// 字段最长 50 字符
                sound: 'message',
                badge: '+1'
              },
              onProgress: function (progress) {
                console.log(progress)
              },
              onSuccess: (message) => {
                this.otherTypesMessagePanelVisible = false
                this.sendMessage(message)
              },
              onFailed: (e) => {
                console.log('error :', e)
              }
            })
          })
        }
      })
    },
    sendOrderMessage(order) {
      //GoEasyIM自定义消息,实现订单发送
      this.$GoEasy.im.createCustomMessage({
        type: 'order',
        payload: order,
        to: this.to,
        notification: {
          title: this.currentUser.name + '发来一个订单',
          body: '[订单消息]',
          sound: 'message',
          badge: '+1'
        },
        onSuccess: (message) => {
          this.otherTypesMessagePanelVisible = false
          this.sendMessage(message)
        },
        onFailed: (e) => {
          console.log('error :', e)
        }
      })
      this.orderList.visible = false
    },
    showActionPopup(message) {
      const MAX_RECALLABLE_TIME = 3 * 60 * 1000 // 3分钟以内的消息才可以撤回
      this.messageSelector.messages = [message]
      if ((Date.now() - message.timestamp) < MAX_RECALLABLE_TIME && message.senderId === this.currentUser.id && message.status === 'success') {
        this.actionPopup.recallable = true
      } else {
        this.actionPopup.recallable = false
      }
      this.actionPopup.visible = true
    },
    hideActionPopup () {
      this.actionPopup.visible = false
      this.actionPopup.message = null
    },
    deleteSingleMessage() {
      uni.showModal({
        content: '确认删除？',
        success: (res) => {
          this.actionPopup.visible = false
          if (res.confirm) {
            this.deleteMessage()
          }
        },
      })
    },
    deleteMultipleMessages() {
      if (this.messageSelector.messages.length > 0) {
        uni.showModal({
          content: '确认删除？',
          success: (res) => {
            this.messageSelector.visible = false
            if (res.confirm) {
              this.deleteMessage()
            }
          },
        })
      }
    },
    deleteMessage() {
      this.$GoEasy.im.deleteMessage({
        messages: this.messageSelector.messages,
        onSuccess: (result) => {
          this.messageSelector.messages.forEach(message => {
            let index = this.history.messages.indexOf(message)
            if (index > -1) {
              this.history.messages.splice(index, 1)
            }
          })
          this.messageSelector.messages = []
        },
        onFailed: (error) => {
          console.log('error:', error)
        }
      })
    },
    recallMessage() {
      this.actionPopup.visible = false
      this.$GoEasy.im.recallMessage({
        messages: this.messageSelector.messages,
        onSuccess: () => {
          console.log('撤回成功')
        },
        onFailed: (error) => {
          console.log('撤回失败,error:', error)
        }
      })
    },
    editRecalledMessage(text) {
      if (this.audio.visible) {
        this.audio.visible = false
      }
      this.text = text
    },
    showCheckBox() {
      this.messageSelector.messages = []
      this.messageSelector.visible = true
      this.actionPopup.visible = false
    },
    selectMessages(e) {
      const selectedMessageIds = e.detail.value
      let selectedMessages = []
      this.history.messages.forEach(message => {
        if (selectedMessageIds.includes(message.messageId)) {
          selectedMessages.push(message)
        }
      })
      this.messageSelector.messages = selectedMessages
    },
    loadHistoryMessage(scrollToBottom) {//历史消息
      this.history.loading = true
      let lastMessageTimeStamp = null
      let lastMessage = this.history.messages[0]
      if (lastMessage) {
        lastMessageTimeStamp = lastMessage.timestamp
      }
      this.$GoEasy.im.history({
        id: this.group.id,
        type: this.$GoEasy.IM_SCENE.GROUP,
        lastTimestamp: lastMessageTimeStamp,
        limit: 10,
        onSuccess: (result) => {
          uni.stopPullDownRefresh()
          this.history.loading = false
          let messages = result.content
          if (messages.length === 0) {
            this.history.allLoaded = true
          } else {
            if (lastMessageTimeStamp) {
              this.history.messages = messages.concat(this.history.messages)
            } else {
              this.history.messages = messages
            }
            if (messages.length < 10) {
              this.history.allLoaded = true
            }
            if (scrollToBottom) {
              this.scrollToBottom()
              //收到的消息设置为已读
              this.markGroupMessageAsRead()
            }
          }
        },
        onFailed: (error) => {
          //获取失败
          console.log('获取历史消息失败:', error)
          uni.stopPullDownRefresh()
          this.history.loading = false
        }
      })
    },
    showMembers() {//显示群成员
      uni.navigateTo({
        url: './member?members=' + JSON.stringify(this.groupMembers)
      })
    },
    onRecordStart() {
      recorderManager.start()
    },
    onRecordEnd() {
      recorderManager.stop()
    },
    showImageFullScreen(e) {
      let imagesUrl = [e.currentTarget.dataset.url]
      uni.previewImage({
        urls: imagesUrl
      })
    },
    //语音录制按钮和键盘输入的切换
    switchAudioKeyboard() {
      if (!this.audio.visible) {
        recorderManager.authorize().then(() => {
          console.log('录音权限获取成功')
          this.audio.visible = true
        }).catch((err) => {
          console.log('err:', err)
          uni.showModal({
            title: '获取录音权限失败',
            content: '请先打开麦克风权限'
          })
        })
      } else {
        this.audio.visible = false
      }
    },
    playVideo(e) {
      this.videoPlayer.visible = true
      this.videoPlayer.url = e.currentTarget.dataset.url
      this.$nextTick(() => {
        this.videoPlayer.context.requestFullScreen({
          direction: 0
        })
        this.videoPlayer.context.play()
      })
    },
    playAudio (audioMessage) {
      let playingMessage = this.audioPlayer.playingMessage

      if (playingMessage) {
        this.audioPlayer.innerAudioContext.stop()
        // 如果点击的消息正在播放，就认为是停止播放操作
        if (playingMessage === audioMessage) {
          return
        }
      }
      this.audioPlayer.playingMessage = audioMessage
      this.audioPlayer.innerAudioContext.src = audioMessage.payload.url
      this.audioPlayer.innerAudioContext.play()
    },
    onVideoFullScreenChange(e) {
      //当退出全屏播放时，隐藏播放器
      if (this.videoPlayer.visible && !e.detail.fullScreen) {
        this.videoPlayer.visible = false
        this.videoPlayer.context.stop()
      }
    },
    messageInputFocusin() {
      this.otherTypesMessagePanelVisible = false
      this.emoji.visible = false
    },
    switchEmojiKeyboard() {
      this.emoji.visible = !this.emoji.visible
      this.otherTypesMessagePanelVisible = false
    },
    showOtherTypesMessagePanel() {
      this.otherTypesMessagePanelVisible = !this.otherTypesMessagePanelVisible
      this.emoji.visible = false
    },
    chooseEmoji(emojiKey) {
      this.text += emojiKey
    },
    showOrderMessageList() {
      this.orderList.orders = []
      this.orderList.visible = true
    },
    hideOrderMessageList() {
      this.orderList.visible = false
    },
    groupCall() {
      uni.showActionSheet({
        itemList: ['视频通话', '语音通话'],
        success: (res) => {
          const mediaType = res.tapIndex === 0 ? 1 : 0
          uni.navigateTo({
            url: `./rtc/group/chooseCallee?mediaType=${mediaType}&groupId=${this.group.id}`,
          })
        },
        fail: (res) => {
          console.log(res.errMsg)
        }
      })
    },
    scrollToBottom() {
      this.$nextTick(() => {
        uni.pageScrollTo({
          scrollTop: 2000000,
          duration: 0
        })
      })
    },
    markGroupMessageAsRead() {
      this.$GoEasy.im.markMessageAsRead({
        id: this.to.id,
        type: this.to.type,
        onSuccess: function () {
          console.log('标记群聊已读成功')
        },
        onFailed: function (error) {
          console.log('标记群聊已读失败:', error)
        }
      })
    }
  }
}
</script>

<style>
page {
    height: 100%;
}
</style>

<style scoped lang="scss">
@import url('./style.scss');
</style>
